html2canvas 和 jspdf

您所在的位置:网站首页 jspdf 分页 html2canvas 和 jspdf

html2canvas 和 jspdf

2024-01-19 00:23| 来源: 网络整理| 查看: 265

需求

好久之前做的两个都是表格数据的页面,客户突然提了个新需求说想要页面能实现下载这两个表格的功能... 实现功能很简单,但是遇到了一个很坑的问题,就是文字被分页截断。所以想总结一下,同时在看一看代码有没有什么可以优化的地方。

我们的项目使用的框架是 Angular, 我就以在 Angular 中的使用举列子。

html2canvas

官网:html2canvas.hertzen.com/

html2canvas 能够实现在用户浏览器端对真整个或者部分页面进行截屏。html2canvas 脚本将当前页面渲染成一个 canvas 图片。通过读取 DOM 并将不同的样式应用到这些元素上面实现。他不需要来自服务器任何渲染,整张图片都是在客户端浏览器创建。 html2canvas 可以通过获取 HTML 的某个 DOM ,然后生成 Canvas, 能让用户保存为图片。这个脚本主要是生成 canvas,那么如果我们需要生成图片还需要将他转化为图片格式.

安装 npm install html2canvas 引用 import html2canvas from 'html2canvas'; html2canvas(downloadElement, options?).then(canvas => {})

options 的值请参考官网

jspdf 安装 npm install jspdf 引用 import jspdf from 'jspdf'; let pdf = new jspdf('p', 'pt', 'a4'); // A4 size page of PDF const imageData = canvas.toDataURL('image/jpeg', 1.0); pdf.addImage(contentDataURL, 'JPEG', 0, 0, imgWidth, imgHeight)

new jspdf(): 第一个参数: 'l' 横向, 'p' 纵向 第二个参数: 测量单位 ("pt","mm", "cm", "m", "in" or "px") 第三个参数: 默认 "A4" 或者 [width, heignt] 还有其他格式。

pdf.addPage() 在 PDF 文档中添加新页面 pdf.addImage(imageData, format, x, y, width, height, alias, compression, rotation) 将图像添加到 PDf

imageData: string | HTMLImageElement | HTMLCanvasElement | Unit8Array format: 'JEPG' | 'PNG' x: x 坐标轴位置 y: y 坐标轴位置 width: 图片长度 height: 图片宽度 alias: 图片别名 compression: 压缩 'FAST' | 'MEDIUM' | 'SLOW' | 'NONE' rotation: 旋转度 (0-359)

删除某页pdf:

let targetPage = pdf.internal.getNumberOfPages(); pdf.deletePage(targetPage)

保存 pdf 文档: pdf.save('name.pdf')

两个插件的使用介绍完了,下面放上我整个实现的 demo 代码

首先,创建一个 dowload pdf 按钮的公共组件 DownloadPDFButtonComponent

down-pdf-button.ts downloadElement 参数为想要下载成 pdf 的 DOM 节点, 可以用 getElementById("idName") 或者 querySelector("#id") 来获取

import { Component, Input, OnInit, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NzMessageService, NzButtonModule, NzIconModule } from 'ng-zorro-antd'; import jspdf from 'jspdf'; import html2canvas from 'html2canvas'; @Component({ selector: 'download-pdf-button', template: ` 下载 PDF `, }) export class DownloadPDFButtonComponent implements OnInit { @Input() pdfName: string = ""; constructor( private _message: NzMessageService ) { } ngOnInit(): void { } downloadPDF(downloadElement) { if (!downloadElement) { return; } const options = { useCORS: true, allowTaint: true }; const A4_WIDTH = 592.28; const A4_HEIGHT = 841; html2canvas(downloadElement, options).then(canvas => { const contentWidth = canvas.width; const contentHeight = canvas.height; // 一页pdf显示html页面生成的canvas高度;(A4) const pageHeight = contentWidth / A4_WIDTH * A4_HEIGHT; // 未生成pdf的html页面高度 let leftHeight = contentHeight; let position = 0; const imgWidth = A4_WIDTH; const imgHeight = A4_WIDTH / contentWidth * contentHeight; const contentDataURL = canvas.toDataURL('image/jpeg', 1.0); let pdf = new jspdf('p', 'pt', 'a4'); // A4 size page of PDF if (leftHeight < pageHeight) { pdf.addImage(contentDataURL, 'JPEG', 0, 0, imgWidth, imgHeight); } else { while (leftHeight > 0) { pdf.addImage(contentDataURL, 'JPEG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight; position -= A4_HEIGHT; //避免添加空白页 if (leftHeight > 0) { pdf.addPage(); } } } pdf.save(this.pdfName + '.pdf'); // Generated PDF }).catch((e) => { }); } } @NgModule({ imports: [CommonModule, NzButtonModule, NzIconModule], exports: [DownloadPDFButtonComponent], declarations: [DownloadPDFButtonComponent] }) export class DownloadPDFModule { }

父组件中引用子组件, 父组件中调用子组件的方法 downloadPDF

parentPdf.html

//balabalabala //balabalabala

parentPdf.ts

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core'; // 引用子组件 import { DownloadPDFButtonComponent } from '../../../common/download/download-pdf-button'; export class ParentPdfComponent implements OnInit { @ViewChild(DownloadPDFButtonComponent) public downloadPdfComponent!: DownloadPDFButtonComponent; downloadPDF(id) { const downloadElement = this.element.nativeElement.querySelector("#" + id); this.downloadPdfComponent.downloadPDF(downloadElement); } } 分页截断文字解决方法

上面的代码就能实现你想要的 DOM 节点下载,很简单。但是,页面数据很多的时候,超过 A4 大小,他就会分页,分页有时候会在有文字的地方截断,这样肯定是不行的。我其实觉得行,但是测试觉得不行,那就不行。我参考了资下面这个博主的思路,根据我们页面的布局又改了一点点。 参考:blog.csdn.net/Sandy_zhi/a… 整个思路大概就是:

在每个不可以被截断的节点添加一个标识类,然后遍历每一个标识类的节点。 根据 DOM 的长度和 A4 长度的比例 * A4 高度就得到 DOM 对应一页 A4 的高度。 然后在遍历节点的时候,判断该节点的头部和尾部是否在一个页面 const topPageNum = Math.ceil((wholeNodes[i].offsetTop + moreHeight) / pageHeight); const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + moreHeight + wholeNodes[i].offsetHeight) / pageHeight); if (bottomPageNum !== topPageNum)

博主的代码里面是没有 moreHeigt 的,但是因为我的页面是由一部分的栅格布局 和 下面4 个 table 组成的,标识类是加到每个 tr 上的, offsetTop 不是到整个 DOM 的高度而是到其父元素的高度, 所以计算topPageNum和bottomPageNum 的时候得加上它上面DOM 的高度。 4. 不在一个页面的话就在改节点前增加一个空白的 tr。

最终的down-pdf-button.ts代码

import { Component, Input, OnInit, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NzMessageService, NzButtonModule, NzIconModule } from 'ng-zorro-antd'; import jspdf from 'jspdf'; import html2canvas from 'html2canvas'; @Component({ selector: 'download-pdf-button', template: ` 下载 PDF `, }) export class DownloadPDFButtonComponent implements OnInit { @Input() pdfName: string = ""; constructor( private _message: NzMessageService ) { } ngOnInit(): void { } downloadPDF(downloadElement, moreHeight) { if (!downloadElement) { return; } const options = { useCORS: true, allowTaint: true }; // 避免笔下误 灯下黑 统一写 const A4_WIDTH = 592.28; const A4_HEIGHT = 841; let pageHeight = (downloadElement.offsetWidth / A4_WIDTH) * A4_HEIGHT; // 将所有不允许被截断的元素进行处理 //let wholeNodes: any = document.querySelectorAll('.whole-node'); let wholeNodes: any = document.querySelectorAll('[class*=whole-node]'); for (let i = 0; i < wholeNodes.length; i++) { let className= wholeNodes[i].className.split(" ")[0]; //两个页面都用到了这个子组件并且两个 pdf 的布局不一样 //page Two我用ng-for遍历实现的 table,每个table长度不一样,所以他们的标识类用 whole-node-1 ... whole-node-4 来表示方便处理 if (this.pdfName.indexOf("pageTwo") > -1 && className.split("-")[2] > 1) { if (className !== wholeNodes[i-1].className.split(" ")[0]) { moreHeight = moreHeight + wholeNodes[i-1].offsetTop + 97; if (className.split("-")[2] < 4) { moreHeight = moreHeight + wholeNodes[i-1].offsetHeight } } } const topPageNum = Math.ceil((wholeNodes[i].offsetTop + moreHeight) / pageHeight); const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + moreHeight + wholeNodes[i].offsetHeight) / pageHeight); if (bottomPageNum !== topPageNum) { //说明该dom会被截断 // 2、插入空白块使被截断元素下移 let divParent = wholeNodes[i].parentNode; let newBlock = document.createElement('tr') newBlock.className = 'emptyDiv' // 3、计算插入空白块的高度 可以适当流出空间使得内容太靠边,根据自己需求而定 let _H = topPageNum * pageHeight - wholeNodes[i].offsetTop - moreHeight; newBlock.style.height = _H + 50 + 'px' divParent.insertBefore(newBlock, wholeNodes[i]) } } html2canvas(downloadElement, options).then(canvas => { let emptyDivs = document.querySelectorAll('.emptyDiv'); for (let i = 0; i < emptyDivs.length; i++) { emptyDivs[i].parentNode.removeChild(emptyDivs[i]) } const contentWidth = canvas.width; const contentHeight = canvas.height; // 一页pdf显示html页面生成的canvas高度;(A4) const pageHeight = contentWidth / A4_WIDTH * A4_HEIGHT; // 未生成pdf的html页面高度 let leftHeight = contentHeight; let position = 0; const imgWidth = A4_WIDTH; const imgHeight = A4_WIDTH / contentWidth * contentHeight; const contentDataURL = canvas.toDataURL('image/jpeg', 1.0); let pdf = new jspdf('p', 'pt', 'a4'); // A4 size page of PDF if (leftHeight < pageHeight) { pdf.addImage(contentDataURL, 'JPEG', 0, 0, imgWidth, imgHeight); } else { while (leftHeight > 0) { pdf.addImage(contentDataURL, 'JPEG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight; position -= A4_HEIGHT; //避免添加空白页 if (leftHeight > 0) { pdf.addPage(); } } } pdf.save(this.pdfName + '.pdf'); // Generated PDF }).catch((e) => { }); } } @NgModule({ imports: [CommonModule, NzButtonModule, NzIconModule], exports: [DownloadPDFButtonComponent], declarations: [DownloadPDFButtonComponent] }) export class DownloadPDFModule { }

放上 page two 的部分 html 代码

{{table.title}} {{header}} {{data[name] | undefinedNullTransform}} {{table.noInfo}}

关于这部分逻辑的代码由于着急提测,所以写的更差劲。如果有更好的解决办法或者代码优化的方案麻烦指教~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3